package com.ys.utils.redis.aop;

import com.alibaba.fastjson.JSON;
import com.ys.utils.base.j2se.*;
import com.ys.utils.spring.properties.Config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 缓存aop拦截器
 */
@Aspect
@Component
@Order(0)
public class RedisContract {
	@Autowired
    private StringRedisTemplate cacheRedis;

    private int sortBei = 10;//短效缓存，没有请求后，过期时效 = 过期时间 * 10
    private int longTime = 12*60*60;//长效缓存，没有请求后，过期失效 = 12个小时
    private ExecutorService execser;	//调度执行相关配置
    private static Set<String> keys = new HashSet<>();//正在异步处理的key

    @PostConstruct
    public void init(){
    	this.sortBei = 10;
    	this.longTime = 43200;
        this.execser = Executors.newFixedThreadPool(256);
    }

    /**
     * 定义一个切入点,所有注解了Redis的方法或类
     */
    @Pointcut("@annotation(com.ys.utils.redis.aop.RedisCache)")
    private void anyMethod(){

    }

    @Around("anyMethod() && @annotation(anno)")
    public Object getRedisData(ProceedingJoinPoint joinPoint, RedisCache anno) throws Throwable {
        //获取缓存key
        String cacheKey = anno.key();
        if(StringHandler.isEmpty(cacheKey)){
        	cacheKey = cacheKey(joinPoint.getTarget().getClass(), joinPoint.getSignature().getName(), joinPoint.getArgs());
        }else{
			cacheKey = cacheKey.contains("#")?parseKey(cacheKey,joinPoint):cacheKey;
		}
        //更新时间不能小于等于0
        if(anno.timeOut() <= 0){
        	throw new RuntimeException(joinPoint.getSignature().getName()+"缓存的更新时间必须大于0");
        }

        //计算缓存过期时间
        int timeout = anno.type() == RedisType.None ? anno.timeOut() : (anno.type() == RedisType.Long ? longTime : anno.timeOut() * sortBei);
        //获取缓存内容
        String value = cacheRedis.opsForValue().get(cacheKey);
        //缓存只要存在数据 就直接返回
        if(StringHandler.isNotEmpty(value)){
			//无缝缓存需要延迟缓存时间
        	if(anno.type() != RedisType.None){
				cacheRedis.expire(cacheKey, timeout, TimeUnit.SECONDS);
			}

            /* 解析缓存数据 */
            Map map = JsonUtil.jsonToBean(value,Map.class);

            //无缝缓存 超过更新时间 又没有在更新中 异步更新
			if(anno.type() != RedisType.None){
				long interval = (System.currentTimeMillis() - StringHandler.getLong(map.get("ct")))/1000;
				if(interval >= anno.timeOut() && keys.add(cacheKey)){
					execser.submit(new CacheRefreshCallable(joinPoint, cacheKey, timeout));
				}
			}
        	Type type = ((MethodSignature) joinPoint.getSignature()).getMethod().getGenericReturnType();
			return type.getTypeName().equals("java.lang.String")?StringHandler.getString(map.get("data")):JsonUtil.gson.fromJson(JsonUtil.gson.toJson(map.get("data")), type);
        }
        //缓存不存在内容 执行方法 存入缓存
        Object result = joinPoint.proceed();
        Map<String, Object> map = new HashMap<>();
        map.put("data", result);
        map.put("ct", System.currentTimeMillis());
        cacheRedis.opsForValue().set(cacheKey,JSON.toJSONString(map),timeout,TimeUnit.SECONDS);
        return result;
    }

    /**
	 * 根据指定的类名、方法名及参数，拼接缓存的实际名称
	 */
	public static String cacheKey(Class<?> clzss, String methodName, Object... args) {
		String cn = getClassName(clzss);
		return cacheKey(cn.replaceAll("\\.",":") + ":" + methodName, args);
	}

	/**
	 * 通过class 获取类名
	 */
	private static String getClassName(Class<?> clzss) {
		String cn = clzss.getName();
		int p = cn.indexOf("$");
		if (p > 1) {
			cn = cn.substring(0, p);
		}
		return cn;
	}

	/**
	 * 解析Spel获取真实key
	 * @param spELString
	 * @param joinPoint
	 * @return
	 */
	private String parseKey(String spELString,ProceedingJoinPoint joinPoint){
		SpelExpressionParser parser = new SpelExpressionParser();
		DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
		Expression expression = parser.parseExpression(spELString);
		EvaluationContext context = new StandardEvaluationContext();
		Object[] args = joinPoint.getArgs();
		for(int i = 0 ; i < args.length ; i++) {
			context.setVariable(paramNames[i], args[i]);
		}
		return expression.isWritable(context)?expression.getValue(context,String.class):expression.getExpressionString();
	}

    /**
     * 解析Spel获取真实where
     * @param spELString
     * @param joinPoint
     * @return
     */
    private Boolean parseWhere(String spELString,ProceedingJoinPoint joinPoint){
        SpelExpressionParser parser = new SpelExpressionParser();
        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
        Expression expression = parser.parseExpression(spELString);
        EvaluationContext context = new StandardEvaluationContext();
        Object[] args = joinPoint.getArgs();
        for(int i = 0 ; i < args.length ; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context,Boolean.class);
    }

	/**
	 * 根据指定的 KEY 拼接缓存的实际名称
	 */
	private static String cacheKey(String key, Object... args) {
		//没有入参 项目名+包名 + 类名
		StringBuilder sbs = new StringBuilder(Logger.getEnv()).append(key);
		if (args == null) {
			return sbs.toString();
		}
		// 将每个参数转换为 json 串，再取 hashcode ，已解决参数一样，缓存的 key 必须一样的问题
		StringBuilder argsbs = new StringBuilder();
		if (args.length == 1 && args[0] instanceof Object[]) {
			Object[] as = (Object[]) args[0];
			argsbs.append(JSON.toJSON(as).hashCode());
		} else {
			argsbs.append(JSON.toJSON(args).hashCode());
		}
		sbs.append(":").append(argsbs.toString().hashCode());
		return sbs.toString();
	}

	/**
	 * 异步刷新redis缓存内存
	 */
	class CacheRefreshCallable implements Callable<Boolean> {

		private int timeout;
		private String cacheKey;
		private ProceedingJoinPoint joinPoint;

		public CacheRefreshCallable(ProceedingJoinPoint joinPoint, String cacheKey, int timeout) {
			this.timeout = timeout;
			this.cacheKey = cacheKey;
			this.joinPoint = joinPoint;
		}

		@Override
		public Boolean call() throws Exception {
			//执行方法 更新缓存
			try {
		        Map<String, Object> map = new HashMap<>();
		        map.put("data", joinPoint.proceed());
		        map.put("ct", System.currentTimeMillis());
				cacheRedis.opsForValue().set(cacheKey,JsonUtil.toJson(map),timeout,TimeUnit.SECONDS);
            } catch (Throwable throwable) {
				Logger.error("异步更新缓存出错：", throwable);
            }
			//删除正在更新的标识
			keys.remove(cacheKey);
			return true;
		}
	}
}
